import logging
import pdb
import unittest
import doctest
from functools import reduce

# 异常捕获(所有的错误类型都继承自BaseException, 捕获父类后将不再捕获子类)
try:
    print('try...')
    r = 10 / int('5')
    print('result:', r)
except ZeroDivisionError as e:     # try 执行出错，跳转至错误处理代码
    print('ZeroDivisionError:', e)
except ValueError as e:
    print('ValueError：', e)
else:                              # 无错误时执行的代码
    print('No error!')
finally:                           # 最终执行的代码(非必要)
    print('final...')


# 记录错误
def func1(s):
    return 10 / int(s)

def bar1(s):
    return func1(s) * 2

def main():
    try:
        bar1('0')
    except Exception as e:
        logging.exception(e)       # 日志记录错误

# main()

class FooError(ValueError):
    pass

def func2(s):
    n = int(s)
    if n == 0:
        raise FooError('Invalid value: %s' % s)
    return 10 / n

# func2('0')

def bar2(s):
    try:
        func2('0')
    except ValueError as e:
        print('ValueError!')
        raise                      # 向更高一级抛出错误(原样抛出)

# bar2('0')

def str2num(s):
    if s.count('.') == 1:
        return float(s)
    else:
        return int(s)
    # return int(s)                # 直接调用此函数会导致运行出错

def calc(exp):
    ss = exp.split('+')
    ns = map(str2num, ss)
    return reduce(lambda acc, x: acc + x, ns)

def calc_func():
    r = calc('100 + 200 + 345')
    print('100 + 200 + 345 =', r)
    r = calc('99 + 88 + 7.6')
    print('99 + 88 + 7.6 =', r)

calc_func()


# 错误调试
def func3(s):
    n = int('s')
    assert n != 0, 'n is zero'     # 断言：表达式的值应当为真，断言失败会抛出AssertionError
    return 10 / n                  # 启动Python解释器时，用参数-O关闭assert

logging.basicConfig(level = logging.INFO)  # 配置logging的输出等级，共4级：debug、info、warning、error
s = '0'                                    # 低于当前输出级别的信息不输出
n = int(s)
logging.info('n = %d' % n)
# print(10 / n)

# pdb.set_trace()             # 程序运行到此处自动暂停

# ================== 单元测试(重要) ==================
class Dict(dict):           # 类的行为和dict一致，但可以通过属性访问
    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, item):
        try:
            return self[item]
        except KeyError:
            raise AttributeError(r'Dict object has no attribute %s' % item)

    def __setattr__(self, key, value):
        self[key] = value

class TestDict(unittest.TestCase):  # 根据是否以test开头的方法判断是否为测试方法，进而决定测试的时候会不会被执行

    def setUp(self):                # 测试开始时运行
        pass

    def tearDown(self):             # 测试结束时运行
        pass

    def test_init(self):
        d = Dict(a = 1, b = 'test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError):   # 期待抛出指定类型的Error
            value = d['empty']

    def test_attrerrot(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

# unittest.main()        # 注意：单元测试和文档测试不能同时运行

# ================== 文档测试(重要) ==================
class MyDict(dict):      # 严格按照Python交互式命令行的输入和输出来判断测试结果是否正确
                         # 测试异常时，可以用...省略中间的输出
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = MyDict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = MyDict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(MyDict, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

def fact(n):
    '''
    Calculate 1*2*...*n

    >>> fact(1)
    1
    >>> fact(10)
    36288000
    >>> fact(-1)
    Traceback (most recent call last):
        ...
    ValueError
    '''
    if n < 1:
        raise ValueError()
    if n == 1:
        return 1
    return n * fact(n - 1)

doctest.testmod()           # 注意：单元测试和文档测试不能同时运行